#include "Chess.hpp"
#include <algorithm>
#include "Position.hpp"
#include "Player.hpp"
#include "King.hpp"
#include "SaveManager.hpp"
#include "Pawn.hpp"
#include "Piece.hpp"
#include "PawnPromotionAction.hpp"
#include "CastlingAction.hpp"
#include "SimulatedMoveAction.hpp"
#include "SimulatedCastlingAction.hpp"

#include <cassert>

#	define	P_NO_PIECE(PLAYER, PIECE)		(PLAYER##_infos.nb_##PIECE##s == 0)
#	define 	P_ONE_PIECE(PLAYER, PIECE)		(PLAYER##_infos.nb_##PIECE##s == 1)

#	define	P1_NO_ROOK()					P_NO_PIECE(p1, rook)
#	define	P2_NO_ROOK()					P_NO_PIECE(p2, rook)
#	define	P1_NO_QUEEN()					P_NO_PIECE(p1, queen)
#	define	P2_NO_QUEEN()					P_NO_PIECE(p2, queen)
#	define	P1_NO_KNIGHT()					P_NO_PIECE(p1, knight)
#	define	P2_NO_KNIGHT()					P_NO_PIECE(p2, knight)
#	define	P1_NO_BISHOP()					P_NO_PIECE(p1, bishop)
#	define	P2_NO_BISHOP()					P_NO_PIECE(p2, bishop)
#	define	P1_NO_PAWN()					P_NO_PIECE(p1, pawn)
#	define	P2_NO_PAWN()					P_NO_PIECE(p2, pawn)

#	define	P1_ONE_ROOK()					P_ONE_PIECE(p1, rook)
#	define	P2_ONE_ROOK()					P_ONE_PIECE(p2, rook)
#	define	P1_ONE_QUEEN()					P_ONE_PIECE(p1, queen)
#	define	P2_ONE_QUEEN()					P_ONE_PIECE(p2, queen)
#	define	P1_ONE_KNIGHT()					P_ONE_PIECE(p1, knight)
#	define	P2_ONE_KNIGHT()					P_ONE_PIECE(p2, knight)
#	define	P1_ONE_BISHOP()					P_ONE_PIECE(p1, bishop)
#	define	P2_ONE_BISHOP()					P_ONE_PIECE(p2, bishop)
#	define	P1_ONE_PAWN()					P_ONE_PIECE(p1, pawn)
#	define	P2_ONE_PAWN()					P_ONE_PIECE(p2, pawn)

#	define 	P1_P2_NO_QUEEN_NO_ROOK_NO_PAWN()																	\
	(P1_NO_PAWN() && P2_NO_PAWN() && P1_NO_QUEEN() && P2_NO_QUEEN() && P1_NO_ROOK() && P2_NO_ROOK())

#	define	KING_VS_KING()																						\
	(P1_NO_KNIGHT() && P2_NO_KNIGHT() && P1_NO_BISHOP() && P2_NO_BISHOP())
	
#	define	KING_AND_BISHOP_VS_KING()																			\
	(P1_NO_KNIGHT() && P2_NO_KNIGHT() && P1_NO_BISHOP() && P2_ONE_BISHOP()) ||									\
	(P1_NO_KNIGHT() && P2_NO_KNIGHT() && P1_ONE_BISHOP() && P2_NO_BISHOP())

#	define	KING_AND_KNIGHT_VS_KING()																			\
	(P1_ONE_KNIGHT() && P2_NO_KNIGHT() && P1_NO_BISHOP() && P2_NO_BISHOP()) ||									\
	(P1_NO_KNIGHT() && P2_ONE_KNIGHT() && P1_NO_BISHOP() && P2_NO_BISHOP())

#	define 	KING_AND_BISHOP_VS_KING_AND_BISHOP()																\
	(P1_NO_KNIGHT() && P2_NO_KNIGHT() && P1_ONE_BISHOP() && P2_ONE_BISHOP())


////////////////////////////////
//// FONCTERS //////////////////
////////////////////////////////

struct PositionEquals : public unary_function<Position*, bool>
{
	const Position* ptr;
	
	PositionEquals(const Position* ptr)
		: ptr(ptr)
	{}
	
	bool operator() (Position* pos)
	{
		return ((*pos) == (*ptr));
	}
};


struct ActionPtrIsNotEqualTo : public unary_function<Action*, bool>
{
	Action* action;
	
	ActionPtrIsNotEqualTo(Action* act)
		: action(act)
	{}
	
	bool operator() (Action* a)
	{
		if (a == action)
			return false;
		else
		{
			delete a;
			return true;
		}
	}
};

struct DelAction : public unary_function<Action*, void>
{
	DelAction()
	{}
	
	void operator()(Action* a)
	{
		delete a;
	}
};

////////////////////////////////
//// PRIVATE STATIC ////////////
////////////////////////////////

Chess* Chess::instance = 0;


////////////////////////////////
//// PUBLIC STATIC /////////////
////////////////////////////////

Chess* Chess::getInstance()
{
	if (Chess::instance == 0)
		Chess::instance = new Chess();

		
	return (Chess::instance);
}

void Chess::init()
{
	chessboard = new Chessboard();
	
	players[Chess::WHITE] = 0;
	players[Chess::BLACK] = 0;
		
	this->reset();
	//calcState();
}

Chess::Color Chess::oppositeColor(Chess::Color color)
{	return (color == Chess::WHITE) ? Chess::BLACK : Chess::WHITE;	}


////////////////////////////////
//// PUBLIC   //////////////////
////////////////////////////////

Chess::~Chess()
{
	try
	{
		if (players[Chess::WHITE] != 0)
			delete players[Chess::WHITE];
		
		if (players[Chess::BLACK] != 0)
			delete players[Chess::BLACK];
			
		delete chessboard;
	}
	catch(Exception* e)
	{	throw e;	}
}

Chessboard*		Chess::getChessboard() const
{
	return (this->chessboard);
}

void Chess::reset()
{
	try
	{
		list<Action*>::iterator		it;
		
		if (players[Chess::WHITE] != 0)
			delete players[Chess::WHITE];
		
		if (players[Chess::BLACK] != 0)
			delete players[Chess::BLACK];
		
		players[Chess::WHITE] = new Player(Chess::WHITE);
		players[Chess::BLACK] = new Player(Chess::BLACK);
				
		state.cgs_cboard = this->chessboard;
		state.cgs_dstate = NONE;
		state.cgs_estate = WAITING_FOR_ACTION;
		state.cgs_player = Chess::BLACK;
		state.cgs_ppawn = 0;
		state.cgs_pstate = STANDARD;
		state.cgs_turn = 0;
		
		for (it = state.cgs_lmoves.begin() ; it != state.cgs_lmoves.end() ; it++)
			delete *it;
			
		state.cgs_lmoves.clear();
		calcState();
	}
	catch(Exception* e)
	{	throw e;	}
}

Chess::ExecError Chess::execute(Action* action)
{
	assert(action != 0);
	
	try
	{		
		switch(state.cgs_estate)
		{
			case Chess::WAITING_FOR_ACTION:
				if (action->getType() == Action::PAWN_PROMOTION)
				{	return (Chess::ILLEGAL_MOVE); }
				
				if (!executeAction(action))
				{
					calcState(false);	
					return (Chess::ILLEGAL_MOVE);
				}
				break;
				
			case Chess::WAITING_FOR_PAWN_PROMOTION:
				if (action->getType() != Action::PAWN_PROMOTION)
				{	return (Chess::PAWN_PROMOTION_REQUIRED);	}
				
				if (!executePawnPromotion(action))
				{	return (Chess::ILLEGAL_PAWN_PROMOTION); }
				break;
				
			case Chess::GAME_OVER:
				return (Chess::GAME_IS_OVER);
		}
		
		calcState(true);
		return(Chess::NO_ERROR);
	}
	catch(Exception* e)
	{	throw e;	}
}

Chess::GameState Chess::getState() const
{
	return (state);
}

void		Chess::save(string filename)
{
	SaveManager::getInstance()->save(filename, players[Chess::WHITE]->getActions(), players[Chess::BLACK]->getActions());
}

void 		Chess::load(string filename)
{
	try
	{	

		
		LoadActions loadactions;

		this->reset();
		SaveManager::getInstance()->load(filename, &loadactions);
		playActions(&loadactions);
	}
	catch(Exception* e)
	{	throw e; }
	
}

Player&	Chess::getPlayer(Chess::Color color)
{
	if (players[color] == 0)
		throw new CriticalException("Chess::getPlayer players not initialized !");
		
	return (*players[color]);
}


////////////////////////////
//// PRIVATE ///////////////
////////////////////////////

Chess::Chess()
{}

bool	Chess::checkCheckForPlayer(Chess::Color color)
{
	list<Position*>				pieces_pos;
	list<Position*>::iterator 	it;
	bool						res = false;
	Position 					king_pos = players[color]->getKing()->getCell()->getPosition();
	
	pieces_pos = players[Chess::oppositeColor(color)]->getAllPossiblePositions();
	
	for (it = pieces_pos.begin() ; it != pieces_pos.end() ; it++)
	{
		if ((**it) == king_pos)
			res = true;
			
		delete (*it);
	}
	
	return (res);
}

list<Action*>	Chess::getLegalMovesForPlayer(Chess::Color color)
{
	try
	{			
		list<Piece*> 				pieces;
		list<Piece*>::iterator		it;
		list<Position*>::iterator	is;
		list<Position*>				positions;
		list<Action*>				legal_moves;
		
		SimulatedMoveAction			move(color, Position(0, 0), Position(0, 0));
		unsigned int				nb_legal_moves = 0;
		unsigned int				nb_moves = 0;
		unsigned int				nb_pieces = 0;
		
		pieces = players[color]->getPieces();
		
		// 1. calcs doable moves (all this stuff can be done faster by multithreading)
		
		// for each piece of player
		for (it = pieces.begin() ; it != pieces.end() ; it++)
		{
			
			if ((*it)->isOut())
				continue;

			positions.clear();
			positions = (*it)->getPossibleCases();
			
			// for each position in which piece can be moved
			for (is = positions.begin() ; is != positions.end() ; is++)
			{
				move.reset((*it)->getCell()->getPosition(), *(*is));
				
				// execute the move
				move.execute();
				
				// if after the move, player is not in check, we save the move
				if (!checkCheckForPlayer(color))
				{
					// the specific position must be saved to
					// piece (to play the turn)
					
					nb_legal_moves++;
					legal_moves.push_back(new SimulatedMoveAction(move));
				}
				
				// in all cases we undo the move
				move.undo();
				nb_moves++;
				
				delete (*is);
			}
			
			nb_pieces++;
		}
		
		// 2. calcs doables castlings
		
		CastlingState 				cstate;
		cstate = players[color]->checkCastling();
		
		if (cstate.castlingPossible())
		{	
			SimulatedCastlingAction	castling(color, CastlingAction::KING_SIDE);
			
			if (cstate.king_side)
			{
				// execute the castling
				castling.execute();
				
				if (!checkCheckForPlayer(color))
					legal_moves.push_back(new SimulatedCastlingAction(castling));

				// undo the castling
				castling.undo();
			}
			
			if (cstate.queen_side)
			{
				// reset the castling
				castling.reset(CastlingAction::QUEEN_SIDE);
				castling.execute();
				
				if (!checkCheckForPlayer(color))
					legal_moves.push_back(new SimulatedCastlingAction(castling));

				castling.undo();
			}
		}
				
		return (legal_moves);
	}
	catch(Exception *e)
	{	throw e;	}
}


void Chess::calcState(bool change_turn)
{
	try
	{
		unsigned int				nb_legal_moves = 0;
		bool						check = false;
		list<Action*>				legal_moves;
		Piece*						ppawn = 0;
				
		state.cgs_pstate = STANDARD;
		state.cgs_dstate = NONE;
		state.cgs_cboard = this->chessboard;
		state.cgs_ppawn = 0;
		
		if ((ppawn = players[state.cgs_player]->checkPromotablePawn()) != 0)
		{
			state.cgs_estate = WAITING_FOR_PAWN_PROMOTION;
			state.cgs_ppawn = ppawn;
		}
		else if (change_turn)
		{
			state.cgs_estate = WAITING_FOR_ACTION;
			
			if (state.cgs_player == Chess::WHITE)
			{
				state.cgs_player = Chess::BLACK;
				players[Chess::WHITE]->incrNbTurns();
			}
			else
			{
				state.cgs_player = Chess::WHITE;
				state.cgs_turn++;
				players[Chess::BLACK]->incrNbTurns();
			}
		}
		
		if (state.cgs_estate == WAITING_FOR_ACTION)
		{
			check = checkCheckForPlayer(state.cgs_player);
			legal_moves = getLegalMovesForPlayer(state.cgs_player);
			
			nb_legal_moves = legal_moves.size();
			
			if (check && (nb_legal_moves == 0))		// PLAYER IS IN CHECK && NO LEGAL MOVES AVAILABLE => CHECKMATE
				state.cgs_pstate = CHECKMATE;
				
			if ((!check) && (nb_legal_moves == 0)) 	// PLAYER ISNT IN CHECK BUT NO LEGAL MOVE AVAILABLE => STALEMATE 
				state.cgs_dstate = STALEMATE;
				
			if (check && (nb_legal_moves > 0))		// PLAYER IS IN CHECK WITH LEGAL MOVE AVAILABLE => CHECK
			{				
				players[state.cgs_player]->incrChecks();
				state.cgs_pstate = CHECK;
			}
			
			// looking for draws
			PlayerPiecesInfos	p1_infos;
			PlayerPiecesInfos	p2_infos;
			
			players[Chess::WHITE]->getPlayerPiecesInfos(&p1_infos);
			players[Chess::BLACK]->getPlayerPiecesInfos(&p2_infos);
			
			if (P1_P2_NO_QUEEN_NO_ROOK_NO_PAWN())	// NO BASIC PIECE AVAILABLE FOR EACH PLAYER
			{
				// see macros
				
				if (KING_VS_KING())
					state.cgs_dstate = CHECKMATE_IMPOSSIBLE;
					
				if (KING_AND_BISHOP_VS_KING())
					state.cgs_dstate = CHECKMATE_IMPOSSIBLE;
					
				if (KING_AND_KNIGHT_VS_KING())
					state.cgs_dstate = CHECKMATE_IMPOSSIBLE;
					
				if (KING_AND_BISHOP_VS_KING_AND_BISHOP())
				{
					if (p1_infos.bishops.front()->getCell()->getColorIndex() == p2_infos.bishops.front()->getCell()->getColorIndex())
						state.cgs_dstate = CHECKMATE_IMPOSSIBLE;
				}
			}
			
			if (state.cgs_dstate != NONE || state.cgs_pstate == CHECKMATE)
			{
				state.cgs_estate = GAME_OVER;
				
				// delete legal moves
				for_each(legal_moves.begin(), legal_moves.end(), DelAction());
				legal_moves.clear();
			}
		}
		
		state.cgs_lmoves = legal_moves;
	}
	catch(Exception* e)
	{	throw e;	}
}

bool Chess::executeAction(Action* action)
{
	try
	{
		MoveAction* 	maction = 0;
		Piece* 			pcaptured = 0;
		CastlingAction* caction = 0;
		list<Action*>::iterator		it;
		list<Action*>				to_del;
		Action*						itaction = 0;
		
		
		assert(action != 0);
				
		for (it = state.cgs_lmoves.begin() ; it != state.cgs_lmoves.end() ; it++)
		{
			itaction = *it;
			
			if (itaction != action)
				to_del.push_back(itaction);
		}
		
		if ((state.cgs_lmoves.size() - to_del.size()) != 1)
			return (false);	
		
		for (it = to_del.begin() ; it != to_del.end() ; it++)
		{	delete *it;		}
		
		state.cgs_lmoves.clear();
		
		switch(action->getType())
		{
			case Action::MOVE:
				assert((maction = static_cast<MoveAction*>(action)) != 0);
				//maction->displayDbg();
				players[state.cgs_player]->addAction(maction);
				maction->execute();
				if ((pcaptured = maction->getPieceCaptured()) != 0)
				{
					players[pcaptured->getColor()]->removePiece(pcaptured);
				}
				break;
				
			case Action::CASTLING:
				// is static cast required here ?
				assert((caction = static_cast<CastlingAction*>(action)) != 0);
				players[state.cgs_player]->addAction(caction);
				caction->execute();
				
				break;
				
			case Action::PAWN_PROMOTION:
				delete action;
				return (false);
		}
		

		
		return (true);
		
	}
	catch(Exception* e)
	{	throw e;	}
}

bool Chess::executePawnPromotion(Action* action)
{
	try
	{
		PawnPromotionAction*	paction = 0;
		
		if (action->getType() != Action::PAWN_PROMOTION)
			return false;
			
		assert((paction = static_cast<PawnPromotionAction*>(action)) != 0);
		players[state.cgs_player]->addAction(paction);
		paction->execute();
		players[state.cgs_player]->removePiece(paction->getPromotedPawn());
		
		return true;
	}
	catch(Exception* e)
	{	throw e;	}
}

void		Chess::playActions(LoadActions* loadactions)
{
	try
	{
		unsigned int i = 1;
		list<Action*>::iterator it;
		Player* p1;
		Player* p2;
		Action*	action = 0;
		PawnPromotionAction*	paction = 0;
		MoveAction*				maction = 0;
		Chess::Color color = Chess::WHITE;
		
		p1 = players[Chess::WHITE];
		p2 = players[Chess::BLACK];
		
		state.cgs_turn = 1;
		
		for ( ; loadactions->p1_actions.find(i) != loadactions->p1_actions.end() ; i++)
		{
			for (it = loadactions->p1_actions[i].begin() ; it != loadactions->p1_actions[i].end() ; it++)
			{
				action = (*it);
				
				switch(action->getType())
				{
					case Action::PAWN_PROMOTION:
							assert((paction = static_cast<PawnPromotionAction*>(action)) != 0);
							p1->addAction(paction);
							paction->execute();	
							p1->removePiece(paction->getPromotedPawn());
							break;
							
					case Action::MOVE:
							assert((maction = static_cast<MoveAction*>(action)) != 0);
							p1->addAction(maction);
							maction->execute();
							if (maction->getPieceCaptured() != 0)
							{	p2->removePiece(maction->getPieceCaptured());	}
							break;
							
					case Action::CASTLING:
							p1->addAction(action);
							action->execute();
							break;
				}
			}
			
			if (loadactions->p2_actions.find(i) == loadactions->p2_actions.end())
			{
				color = Chess::BLACK;
				break;
			}
			
			for (it = loadactions->p2_actions[i].begin() ; it != loadactions->p2_actions[i].end() ; it++)
			{
				action = (*it);
				
				switch(action->getType())
				{
					case Action::PAWN_PROMOTION:
							assert((paction = static_cast<PawnPromotionAction*>(action)) != 0);
							p2->addAction(paction);
							paction->execute();	
							p2->removePiece(paction->getPromotedPawn());
							break;
							
					case Action::MOVE:
							assert((maction = static_cast<MoveAction*>(action)) != 0);
							p2->addAction(maction);
							maction->execute();
							if (maction->getPieceCaptured() != 0)
							{	p1->removePiece(maction->getPieceCaptured());	}
							break;
							
					case Action::CASTLING:
							p2->addAction(action);
							action->execute();
							break;
				}
			}
						
			state.cgs_turn++;
		}
		
		
		if (color == Chess::WHITE)
		{
			state.cgs_player = Chess::BLACK;
			state.cgs_turn--;
		}
		else
			state.cgs_player = Chess::WHITE;
		
		calcState(true);
	}
	catch(Exception* e)
	{	throw e; }
}



